/*******************************************************\
|* Author: Trevor Sundberg
\*******************************************************/

// Header protection
#pragma once
#ifndef SKUGO_TYPE_BINDING_HPP
#define SKUGO_TYPE_BINDING_HPP

// Includes
#include "SkugoRtti.hpp"

namespace Skugo
{
  // Defines
  #define SkugoTypeId(type) (TypeBinding::StaticTypeId<type>::Get())

  // Derive from object
  class IObject
  {
  public:
    // Declare a virtual destructor
    virtual ~IObject() {}

    // Get the most derived type from an instance of the class (usually a base pointer)
    virtual const TypeInfo* GetDerivedType() = 0;
  };

  // All things relevant to binding types
  class TypeBinding
  {
  public:
    // Initialize all queued types
    static void InitializeAllTypes();

    // An efficient hash algorithm for c-strings strings
    static int64 HashString(const char* string, int64 length);
    
    // Check if a type is derived from a given base
    static bool IsDerivedFrom(const TypeInfo* type, const TypeInfo* base);

    // Dynamically cast a base type into a derived type
    template <typename Derived, typename Base>
    static Derived* DynamicCast(Base baseClassPointer)
    {
      // If the cast is safe, then return the casted pointer
      if (IsDerivedFrom(SkugoTypeId(Derived), baseClassPointer->GetDerivedType()))
        return static_cast<Derived*>(baseClassPointer);

      // Otherwise the cast failed, return a NULL pointer
      return NULL;
    }

  public:
    // Type-defines
    typedef void (*SetupTypeFn)(TypeInfo& type);

    // Create the type (the parent can be NULL if the type is a base)
    static TypeInfo MakeType
    (
      StringParam     name,
      sizeType        size,
      const TypeInfo* parent,
      SetupTypeFn     setup
    );

    // A template for grabbing a TypeInfo given a template type
    // (This template can be specialized using macros)
    // Tthis is to allow Rtti for pre-defined types such as int, via specialization
    template <typename T>
    struct StaticTypeId
    {
      static const TypeInfo* Get()
      {
        // If no specialization was made, then we expect that the class we're trying
        // to get a type-info from has a 'GetStaticType' function
        return T::GetStaticType();
      }
    };

    // A partial specialization for reference types
    template <typename T>
    struct StaticTypeId<T&>
    {
      static const TypeInfo* Get()
      {
        // Forward to the type that is being referenced
        return SkugoTypeId(T);
      }
    };

    // A partial specialization for pointer types
    template <typename T>
    struct StaticTypeId<T*>
    {
      static const TypeInfo* Get()
      {
        // Forward to the type that is being pointed at
        return SkugoTypeId(T);
      }
    };

    // Add to the initializers (return an int so we can use it globally)
    template <typename T>
    static int AddInit()
    {
      return AddInitializer(TypeBinding::StaticTypeId<T>::Get);
    }

  private:

    // Type-defines
    typedef const TypeInfo* (*InitTypeFn)();
    typedef vector<InitTypeFn> InitList;

    // Get the initializer list
    static InitList& GetInitializers();

    // Add to the initializers (return an int so we can use it globally)
    static int AddInitializer(InitTypeFn fn);
  };

  // Convert an bound object instance to a runtime pointer
  // This uses a little bit of 'substitution failure is not an error' since this
  // templated function needs to have slightly different behavior for when the type
  // was declared within the class vs when it was declared externally
  // SFINAE comes into play when we try to infer type T using "typename T::ThisType"
  template <typename T>
  RuntimePointer ToRuntimePointer(typename T::ThisType* instance)
  {
    // Create the runtime pointer using a virtual derived type
    RuntimePointer pointer;
    pointer.Instance = instance;
    pointer.Type = instance->GetDerivedType();
    return pointer;
  }

  // Convert an bound object instance to a runtime pointer (general version)
  template <typename T>
  RuntimePointer ToRuntimePointer(T* instance)
  {
    // Create the runtime pointer using the static type
    RuntimePointer pointer;
    pointer.Instance = instance;
    pointer.Type = SkugoTypeId(T);
    return pointer;
  }
  
  // A helper macro for creating the static type-info
  // Note: 'declType' is the type that we defined the 'SetupType' function on
  #define ReturnTypeInfo(thisType, declType, parentTypeInfo)                      \
    /* Create and return an address to the static type */                         \
    static TypeInfo instance = TypeBinding::MakeType                              \
      (#thisType, sizeof(thisType), parentTypeInfo, declType::SetupType);         \
    return &instance;   

  /********************************** EXTERNAL ***********************************/

  // Declare an external type
  #define SkugoDeclareExternalType(thisType)                                      \
    /* A specialization so we know that type info exists for this type */         \
    template <>                                                                   \
    struct TypeBinding::StaticTypeId<thisType>                                    \
    {                                                                             \
      static const TypeInfo* Get();                                               \
    };                                                                            \
    /* Create a small struct that defines the binding function */                 \
    struct thisType##TypeBinding                                                  \
    {                                                                             \
      /* Binding macros need the current class being bound */                     \
      typedef thisType  ThisType;                                                 \
      typedef thisType* ThisPtrType;                                              \
      /* External types still need a way to setup thier bindings */               \
      static void SetupType(TypeInfo& type);                                      \
    };

  // (Helper) Define an external type
  #define SkugoDefineExternalHelper(thisType, parentTypeInfo)                     \
    /* Implementation of the 'get type' specialization */                         \
    const TypeInfo* TypeBinding::StaticTypeId<thisType>::Get()                    \
    {                                                                             \
      /* Create and return an address to the static type */                       \
      ReturnTypeInfo(thisType, thisType##TypeBinding, parentTypeInfo);            \
    }                                                                             \
    /* Add to the global initalizers, so we can ensure initialization of types */ \
    int thisType##Init = TypeBinding::AddInit<thisType>();                        \
    /* Implementation of the binding (the user provides the body) */              \
    void thisType##TypeBinding::SetupType(TypeInfo& type)


  // Define an external base type
  #define SkugoDefineExternalBaseType(thisType)                                   \
    SkugoDefineExternalHelper(thisType, NULL)

  // Define an external derived type
  #define SkugoDefineExternalDerivedType(thisType, parentType)                    \
    SkugoDefineExternalHelper(thisType, TypeId(parentType))


  /************************************ CLASS ************************************/

  // Declare a type (belongs inside the type definition)
  #define SkugoDeclareType(thisType)                                              \
    /* Get the most derived type from an instance of the class */                 \
    /* Inheriting from 'IObject' will make these virtual */                       \
    /*virtual*/ const TypeInfo* GetDerivedType();                                 \
    /* Get the static type id from the object */                                  \
    static const TypeInfo* GetStaticType();                                       \
    /* Binding macros need the current class being bound */                       \
    typedef thisType  ThisType;                                                   \
    typedef thisType* ThisPtrType;                                                \
    /* Implicit conversion to a runtime pointer, for ease of use */               \
    inline operator RuntimePointer();                                             \
    /* Setup any bindings that may be necessary for this type */                  \
    static void SetupType(TypeInfo& type);

  // (Helper) Define a type
  #define SkugoDefineHelper(thisType, parentTypeInfo)                             \
    /* Implementation of the get derived type function */                         \
    const TypeInfo* thisType::GetDerivedType()                                    \
    {                                                                             \
      return thisType::GetStaticType();                                           \
    }                                                                             \
    /* Get the static type id from the object */                                  \
    const TypeInfo* thisType::GetStaticType()                                     \
    {                                                                             \
      /* Create and return an address to the static type */                       \
      ReturnTypeInfo(thisType, thisType, parentTypeInfo);                         \
    }                                                                             \
    /* Implicit conversion to a runtime pointer, for ease of use */               \
    thisType::operator RuntimePointer()                                           \
    {                                                                             \
      return ToRuntimePointer<thisType>(this);                                    \
    }                                                                             \
    /* Add to the global initalizers, so we can ensure initialization of types */ \
    int thisType##Init = TypeBinding::AddInit<thisType>();                        \
    /* Implementation of the binding (the user provides the body) */              \
    void thisType::SetupType(TypeInfo& type)

  // Define a base type
  #define SkugoDefineBaseType(thisType)                                           \
    SkugoDefineHelper(thisType, NULL)

  // Define a derived type
  #define SkugoDefineDerivedType(thisType, parentType)                            \
    SkugoDeclareHelper(thisType, TypeId(parentType))


  /********************************** PRIMITIVE **********************************/

  // Declare a primitive type
  #define SkugoDeclarePrimitiveType(thisType)                                     \
    SkugoDeclareExternalType(thisType)

  // Define a primitive type
  #define SkugoDefinePrimitiveType(thisType)                                      \
    SkugoDefineExternalBaseType(thisType)                                         \
    {                                                                             \
      Primitives::Setup<thisType>(type);                                          \
    }

  // Declaration of all primitive types (these MUST be seen by anything
  // using type-binding, and therefore they are declared within this file)
  SkugoDeclarePrimitiveType(char);
  SkugoDeclarePrimitiveType(short);
  SkugoDeclarePrimitiveType(int);
  SkugoDeclarePrimitiveType(long);
  SkugoDeclarePrimitiveType(int64);
  SkugoDeclarePrimitiveType(uchar);
  SkugoDeclarePrimitiveType(ushort);
  SkugoDeclarePrimitiveType(uint);
  SkugoDeclarePrimitiveType(ulong);
  SkugoDeclarePrimitiveType(uint64);
  SkugoDeclarePrimitiveType(float);
  SkugoDeclarePrimitiveType(double);
  SkugoDeclarePrimitiveType(bool);
}

// End header protection
#endif